import {
  IconPlayerSkipBack,
  IconPlayerTrackNext,
  IconPlayerTrackPrev,
} from '@tabler/icons';
import { preview } from 'preview';
import * as React from 'react';
import { shape } from 'shape';
import { MODE } from '../../shared/constants';
import { InvalidDependentPanelError } from '../../shared/errors';
import {
  PanelInfo,
  PanelInfoWidth,
  PanelResult,
  TableColumn,
  TablePanelInfo,
} from '../../shared/state';
import { columnsFromObject } from '../../shared/table';
import { panelRPC } from '../asyncRPC';
import { Alert } from '../components/Alert';
import { Button } from '../components/Button';
import { FieldPicker } from '../components/FieldPicker';
import { FormGroup } from '../components/FormGroup';
import { PanelSourcePicker } from '../components/PanelSourcePicker';
import { Radio } from '../components/Radio';
import { Select } from '../components/Select';
import { PanelBodyProps, PanelDetailsProps, PanelUIDetails } from './types';

export async function evalColumnPanel(
  panelId: string,
  panelSource: string,
  columns: Array<string>,
  _: Record<string | number, string>,
  panels: Array<PanelInfo>
) {
  if (MODE === 'browser') {
    const lastRun = new Date();
    const panelIndex = (panels || []).findIndex(function findIndex(p) {
      return p.id === panelSource;
    });
    const panel = panels[panelIndex];
    const resultMeta = panel?.resultMeta;
    if (!resultMeta || !resultMeta?.value) {
      throw new InvalidDependentPanelError(panelSource);
    }
    const { value } = resultMeta;
    const valueWithRequestedColumns = columnsFromObject(
      value,
      columns,
      panelSource,
      panel.page,
      panel.pageSize
    );
    const s = shape(valueWithRequestedColumns);
    return {
      value: valueWithRequestedColumns,
      preview: preview(valueWithRequestedColumns),
      shape: s,
      loading: false,
      stdout: '',
      lastRun,
      elapsed: new Date().valueOf() - lastRun.valueOf(),
      size: value ? JSON.stringify(value).length : 0,
      arrayCount: s.kind === 'array' ? (value || []).length : null,
      contentType: 'application/json',
    };
  }

  return await panelRPC('eval', panelId);
}

function mapColumnToField(c: TableColumn) {
  return c.field;
}

export function evalTablePanel(
  panel: TablePanelInfo,
  panels: Array<PanelInfo>,
  idMap: Record<string | number, string>
) {
  return evalColumnPanel(
    panel.id,
    panel.table.panelSource,
    panel.table.columns.map(mapColumnToField),
    idMap,
    panels
  );
}

export function TablePanelDetails({
  panel,
  panels,
  updatePanel,
}: PanelDetailsProps<TablePanelInfo>) {
  const data =
    (
      (panels || []).find(function mapPanelsToResults(p) {
        return p.id === panel.table.panelSource;
      }) || {}
    ).resultMeta || new PanelResult();

  return (
    <React.Fragment>
      <FormGroup>
        <div className="form-row">
          <PanelSourcePicker
            currentPanel={panel.id}
            panels={panels}
            value={panel.table.panelSource}
            onChange={function handlePanelSourceChange(value: string) {
              panel.table.panelSource = value;
              updatePanel(panel);
            }}
          />
        </div>
        <div className="form-row">
          <Radio
            label="Row Numbers"
            value={String(panel.table.rowNumbers)}
            onChange={(value: string) => {
              panel.table.rowNumbers = value === 'true';
              updatePanel(panel);
            }}
            options={[
              { label: 'Visible', value: 'true' },
              { label: 'Hidden', value: 'false' },
            ]}
          />
        </div>
        <div className="form-row">
          <Radio
            label="Width"
            value={panel.table.width}
            onChange={(value: string) => {
              panel.table.width = value as PanelInfoWidth;
              updatePanel(panel);
            }}
            options={[
              { label: 'Default', value: 'small' },
              { label: '75%', value: 'medium' },
              { label: '100%', value: 'large' },
            ]}
          />
        </div>
      </FormGroup>
      <FormGroup>
        {panel.table.columns.map(function mapColumnRender(c, i) {
          return (
            <div
              className="form-row form-row--multi vertical-align-center"
              key={c.field + i}
            >
              <FieldPicker
                onDelete={
                  panel.table.columns.length > 1
                    ? function handleColumnDelete() {
                        panel.table.columns.splice(i, 1);
                        updatePanel(panel);
                      }
                    : undefined
                }
                label="Column"
                value={c.field}
                shape={data?.shape}
                onChange={function handleFieldChange(value: string) {
                  c.field = value;
                  updatePanel(panel);
                }}
                labelValue={c.label}
                labelOnChange={function handleFieldLabelChange(value: string) {
                  c.label = value;
                  updatePanel(panel);
                }}
              />
            </div>
          );
        })}
        <Button
          onClick={function handleAddColumn() {
            panel.table.columns.push({ label: '', field: '' });
            updatePanel(panel);
          }}
        >
          Add Column
        </Button>
      </FormGroup>
    </React.Fragment>
  );
}

function formatCell(c: any): React.ReactNode | string {
  if (c === undefined || c === null) {
    return <small className="text-muted">(Empty)</small>;
  }

  if (typeof c === 'string' || typeof c === 'number') {
    return String(c);
  }

  return JSON.stringify(c);
}

export function TablePanel({
  panel,
  updatePanel,
}: PanelBodyProps<TablePanelInfo>) {
  const data = panel.resultMeta || new PanelResult();

  let valueAsArray: Array<any> = [];
  if (data && data.value && Array.isArray(data.value)) {
    valueAsArray = data.value;
  }

  if (!panel.table.columns || !panel.table.columns.length) {
    return (
      <Alert type="info">
        There are no columns to display. Add columns in the panel details.
      </Alert>
    );
  }

  // column key is (field + i) everywhere because columns can be
  // duplicated. Maybe should assign a uuid to them instead
  return (
    <div>
      <table className={`table table--${panel.table.width}`}>
        <thead>
          <tr>
            {panel.table.rowNumbers ? <th></th> : null}
            {panel.table.columns.map(function mapColumnToHeader(
              column: TableColumn,
              i: number
            ) {
              return <th key={column.field + i}>{column.label}</th>;
            })}
          </tr>
        </thead>
        <tbody>
          {valueAsArray.map(function mapRows(row: any, i: number) {
            return (
              /* probably a better way to do this... */ <tr
                key={Object.values(row).join(',') + i}
              >
                {panel.table.rowNumbers ? (
                  <td
                    className="text-muted"
                    style={{
                      width: 0 /* magically uses up least amount of space needed */,
                    }}
                  >
                    <small>#{panel.page * panel.pageSize + i + 1}</small>
                  </td>
                ) : null}
                {panel.table.columns.map(function mapColumnToCell(
                  column: TableColumn,
                  i: number
                ) {
                  return (
                    <td key={column.field + i}>
                      {formatCell(row[column.field])}
                    </td>
                  );
                })}
              </tr>
            );
          })}
        </tbody>
      </table>

      <div className="vertical-align-center table-controls">
        <Button
          onClick={() => {
            panel.page = 0;
            updatePanel(panel);
          }}
          icon
          disabled={panel.page === 0}
          className="mr-1"
          title="Back to first page"
        >
          <IconPlayerSkipBack />
        </Button>
        <Button
          onClick={() => {
            panel.page--;
            updatePanel(panel);
          }}
          icon
          disabled={panel.page === 0}
          className="mr-1"
          title="Previous Page"
        >
          <IconPlayerTrackPrev />
        </Button>
        <Button
          icon
          onClick={() => {
            panel.page++;
            updatePanel(panel);
          }}
          disabled={valueAsArray?.length < panel.pageSize}
          title="Next Page"
        >
          <IconPlayerTrackNext />
        </Button>
        <div className="flex-right">
          <Select
            onChange={(v: string) => {
              panel.pageSize = +v || 15;
              updatePanel(panel);
            }}
            label="Page Size"
            value={String(panel.pageSize)}
          >
            <option value="15">15</option>
            <option value="100">100</option>
            <option value="1000">1000</option>
          </Select>
        </div>
      </div>
    </div>
  );
}

export const tablePanel: PanelUIDetails<TablePanelInfo> = {
  icon: 'table_chart',
  eval: evalTablePanel,
  id: 'table',
  label: 'Table',
  details: TablePanelDetails,
  body: TablePanel,
  previewable: false,
  factory: function (pageId: string, name: string) {
    return new TablePanelInfo(pageId, { name });
  },
  hasStdout: false,
  info: null,
  dashboard: true,
};
